package org.me.marble;

import java.util.*;

/**
*
* @author tungi
*/

public class PointGenerator {
	private ArrayList<Vector> lastGeneratedPoints;
	private Random random;
	private int[] segmentation;
	private Vector direction=new Vector(0,0,-1);
	
	public PointGenerator(){
		lastGeneratedPoints=new ArrayList<Vector>();
		segmentation=new int[]{0,-1,1,0,-1,1,0,-1,1};
		
		random=new Random();
		for(int i=0;i<random.nextInt(30);++i){
			int from=random.nextInt(9);
			int to=random.nextInt(9);
			int fromInt=segmentation[from];
			int toInt=segmentation[to];
			segmentation[from]=toInt;
			segmentation[to]=fromInt;
		}
	}
	
	public void generatePointObjects(ArrayList<AvailableObject> objects,
			ArrayList<Triangle> triangles,Vector gateCoordinate){
		int index=0;
		lastGeneratedPoints.add(0,new Vector(0.0f,0.0f,-5.0));
		while(true){
			//->just for sure
			if(index==triangles.size())
				break;
			//get the direction of the squad
			direction=Vector.Minus(
                    Vector.Plus(triangles.get(index+1).getPoint(0),
                            triangles.get(index+1).getPoint(2)),
                    Vector.Plus(triangles.get(index).getPoint(2),
                            triangles.get(index).getPoint(0)));
			direction.Normalize();
			//generate new direction, -> there are three directions
			//available: left, right, no change
			int dir=segmentation[(int)(Math.random()*9)];	
			
			//search the last point object
			Vector last_point=getLastPoint(triangles,index);
			
			//get the new point
			Vector projectedPoint=getNextPoint(dir,triangles,index,last_point);			
			
			//now we need to checks if we reach the edge of the path
			Vector p0=new Vector(triangles.get(index).getPoint(0));p0.setY(0);
			Vector p1=new Vector(triangles.get(index).getPoint(1));p1.setY(0);
			Vector p2=new Vector(triangles.get(index+1).getPoint(0));p2.setY(0);
			Vector p3=new Vector(triangles.get(index+1).getPoint(1));p3.setY(0);
			if(Geometry.getPointLineDistance(p0,p1,projectedPoint)<0.3f 
					|| Geometry.getPointLineDistance(p3,p2,projectedPoint)<0.3f){
				//we need to generate a new direction
				int g=random.nextInt(2);
				switch(g){
					case 0: dir=0;
							projectedPoint=getNextPoint(dir,triangles,index,last_point);
					break;
					case 1: dir*=-1;
							projectedPoint=getNextPoint(dir,triangles,index,last_point);
					break;
					default:break;
				}
			}		
			
			//if we reach the gate, return			
			if(Vector.Minus(gateCoordinate,projectedPoint).Length()<1)
				break;
			
			boolean success=selectTriangle(triangles,objects,projectedPoint,index);
			//otherwise increase index
			if(!success){
				if(lastGeneratedPoints.size()==index){
					Vector v=Vector.Plus(triangles.get(index).getPoint(0),triangles.get(index).getPoint(2));
					v.Multiplication(0.5);
					lastGeneratedPoints.add(index,v);
					lastGeneratedPoints.add(index+1,v);
				}
				else
					index+=2;
								
				//last_point=getLastPoint(triangles,index);
				//projectedPoint=getNextPoint(0,triangles,index,last_point);
				//selectTriangle(triangles,objects,projectedPoint,index);
			}
		}
	}
	
	private Vector getLastPoint(ArrayList<Triangle> triangles,int index){
		Vector ret;
		int i=0;
		try{
			return lastGeneratedPoints.get(index);
		}catch(Exception ex){
			int x=index-triangles.get(index).getBackJump();
			while(true){
				ret=lastGeneratedPoints.get(x);					
				if(ret!=null)
					break;
				i++;
				x-=triangles.get(index-i).getBackJump();
			}
			return ret;
		}
	}
	
	private Vector getNextPoint(int dir,ArrayList<Triangle> triangles,int index,Vector last_point){
		//now calculate a new direction
		Vector modifiedDir=Geometry.rotateVector(dir*Math.PI/15, 
				direction,triangles.get(index).getNormal());
		modifiedDir.Normalize();		
		//then a projected coordinate's of the new point
		return new Vector(
				last_point.getX()+modifiedDir.getX()*0.8f,0,
				last_point.getZ()+modifiedDir.getZ()*0.8f
		);	
	}
	
	private boolean selectTriangle(ArrayList<Triangle> triangles,
			ArrayList<AvailableObject> objects,Vector projectedPoint,int index){
		Vector realPoint=new Vector();
		//we need the backjump of this triangle
		int backjump=triangles.get(index).getBackJump();
		//if the new point is in the current triangle, place it
		if(triangles.get(index).isPartProj(projectedPoint)){
			//calculate the real point
			realPoint=Geometry.getProjectionToPlane(
					triangles.get(index).getNormal(), 
					triangles.get(index).getPoint(0),projectedPoint);		
			addNewPointObject(index,realPoint,objects);
		}
		else if(triangles.get(index+1).isPartProj(projectedPoint)){
			//calculate the real point
			realPoint=Geometry.getProjectionToPlane(
					triangles.get(index+1).getNormal(), 
					triangles.get(index+1).getPoint(0),projectedPoint);
			addNewPointObject(index,realPoint,objects);
		}
		else if(index>0 && triangles.get(index-backjump).isPartProj(projectedPoint)){
			//calculate the real point
			realPoint=Geometry.getProjectionToPlane(
					triangles.get(index-backjump).getNormal(), 
					triangles.get(index-backjump).getPoint(0),projectedPoint);		
			addNewPointObject(index-backjump-1,realPoint,objects);
		}
		else if(index>0 && triangles.get(index-backjump-1).isPartProj(projectedPoint)){
			//calculate the real point
			realPoint=Geometry.getProjectionToPlane(
					triangles.get(index-backjump).getNormal(), 
					triangles.get(index-backjump).getPoint(0),projectedPoint);		
			addNewPointObject(index-backjump-1,realPoint,objects);
		}
		else return false;
		return true;
	}
	
	private void addNewPointObject(int index,Vector realPoint,ArrayList<AvailableObject> objects){
		try{
			lastGeneratedPoints.remove(index);
			lastGeneratedPoints.remove(index);
		}catch(IndexOutOfBoundsException iobx){}
		lastGeneratedPoints.add(index,realPoint);
		lastGeneratedPoints.add(index+1,realPoint);
		
		float width=0.5f*(1-((float)OptionsLibrary.difficulty_level-1f)/(float)OptionsLibrary.MAX_DIFFICULTY);
		AvailableObject ao=new PointObject2(gameObjects.PointObject2.ordinal(),width);
		objects.add(ao);
        ao.centre=new Vector(realPoint);
	}
}
